/*
 * Socket.h
 *
 *  Created on: Jun 22, 2016
 *      Author: Lucifer
 */

#ifndef BFX_NET_SOCKET_H_
#define BFX_NET_SOCKET_H_

#include "./Common.h"
#include "./RCObject.h"
#include "./SocketError.h"

#if PLATFORM == PLATFORM_WINDOWS
#include <WinSock2.h>
#include <Ws2ipdef.h>
#include <stdint.h>
typedef SOCKET fildes_t;
#define INVALID_FILDES INVALID_SOCKET
#else
#include <netinet/in.h>
typedef int fildes_t;
#define INVALID_FILDES ((fildes_t) (~0))
#endif

namespace BFX {

////////////////////////////////////////////////////////////////////////////////
///
class InetAddress: public RCObject {
public:
	/// Specify the address family
	enum AddressFamily {
		AF_Unspecified = AF_UNSPEC,	/// Unspecified
		AF_IPv4 = AF_INET,	/// Internet Protocol, Version 4
		AF_IPv6 = AF_INET6	/// Internet Protocol, Version 6
	};
public:
	explicit InetAddress(const sockaddr* pAddr = nullptr);
	InetAddress(uint16_t port, bool loopback = false, AddressFamily addressFamily = AF_IPv4);
	InetAddress(const char* address, uint16_t port, AddressFamily addressFamily = AF_IPv4);

	const sockaddr* getSockAddr() const {
		return static_cast<struct sockaddr*>((void*) (&_addr));
	}
	int getSockAddrSize() const {
		AddressFamily af = getAddressFamily();
		return (af == AF_INET) ?
				sizeof(sockaddr_in) : ((af == AF_INET6) ? sizeof(sockaddr_in6) : -1);
	}
	AddressFamily getAddressFamily() const {
		return (AddressFamily) _addr.sin_family;
	}
	bool isValid() const {
		return (AF_Unspecified != getAddressFamily());
	}

private:
	union {
		sockaddr_in _addr;
		struct sockaddr_in6 _addr6;
	};
};

////////////////////////////////////////////////////////////////////////////////
///

class Socket: public RCObject {
	/// Specifies the type of shutdown. The values are as follows:
	enum ShutdownType {
		ST_Receives = 0,	// SHUT_RD: Disables further receive operations.
		ST_Sends = 1,		// SHUT_WR: Disables further send operations.
		ST_Both = 2			// SHUT_RDWR: Disables further send and receive operations.
	};
public:
	Socket();
	explicit Socket(InetAddress* addr);
	virtual ~Socket();

	fildes_t getDescriptor() const {
		return _fd;
	}
	bool isValid() const {
		return (_fd != INVALID_FILDES);
	}

	bool bind(InetAddress* addr);
	bool listen(int backlog = SOMAXCONN);
	bool connect(InetAddress* addr);
	bool accept(Socket* rSocket, InetAddress* rAddr = nullptr);

	int send(const void* buf, int bufLen, int flags = 0);
	int receive(void* buf, int bufLen, int flags = 0);

	bool getSockName(InetAddress* rAddr);

	bool shutdown(ShutdownType how = ST_Both);
	void close();

	bool setSockOpt(int optionName, const void* optionValue, int optionLen, int level = SOL_SOCKET);
	bool getSockOpt(int optionName, void* optionValue, int* optionLen, int level = SOL_SOCKET);

	bool ioCtl(long command, void* arg);

	bool setNonBlocking(bool state);
	bool setReuseAddress(bool state);
	bool setReusePort(bool state);
	bool setKeepAlive(bool state);
	bool setTcpNoDelay(bool state);

private:
	bool attach(fildes_t fd);

	bool initialize(InetAddress::AddressFamily family);

private:
	fildes_t _fd;
};

} /* namespace BFX */

#endif /* BFX_NET_SOCKET_H_ */
